package interaction.game;

import interaction.data.DataSource;
import interaction.drawable.Drawable;
import interaction.input.concrete.GameComponent;
import processing.core.*;
import gifAnimation.*;
import java.awt.Rectangle;

public class Hero extends BaseGameObject {
	private PVector acceleration = new PVector();
	private final float GROUND_LEVEL;
	private final int MAX_VELOCITY = 30;
	private final float MAX_ACCELERATION = 5;
	
	private final float GRAVITY = (float) 0.45;
	private final float JUMP_VELOCITY = -10;
	private final float RUN_ACCELERATION = (float) 7;
	private final float GROUND_FRICTION = (float) -0.0005;
	private DataSource<Boolean> isAlive = new DataSource<Boolean>(true);
	private DataSource<Boolean> isOnScreen = new DataSource<Boolean>(true);
	
	private float frameSpeed = 0;
	
	private Animation running;
	
	public Hero(Animation animation, PVector position) {
		super(animation.getCurrentFrame(), position, new PVector());
		this.running = animation;
		
		GROUND_LEVEL = position.y;
	}
	
	public PVector getAcceleration() {
		return acceleration;
	}
	
	public DataSource<Boolean> getIsAliveDataSource() {
		return isAlive;
	}
	
	public DataSource<Boolean> getIsOnScreenDataSource() {
		return isOnScreen;
	}
	
	public boolean isAlive()
	{
		return isAlive.get();
	}
	
	public boolean isDead(){
		return !isAlive();
	}
	
	// Increases velocity by set amount to the positive x direction
	public void run() {
		if (isAlive()) {
			getVelocity().x += RUN_ACCELERATION;
			checkVelocity();
		}
	}
	
	// Causes the hero to jump by giving him upward velocity
	public void jump(){
		if (isAlive()) {
			getVelocity().y = JUMP_VELOCITY;
			getAcceleration().y = GRAVITY;
		}
	}
	
	// Updates the hero's position and speed
	public void update(){
		if (isAlive()) {
			updateAlive();
		} else {
			updateDead();
		}
	}
	
	public void updateAlive()
	{
		applyAcceleration();
		applyVelocity();
		advanceAnimationFrame();
		applyNegativeAcceleration();
	}
	
	public void applyAcceleration()
	{
		getVelocity().add(getAcceleration());
		checkVelocity();
	}
	
	public void checkVelocity()
	{
		PVector velocity = getVelocity();
		if (velocity.x > MAX_VELOCITY) {
			// Cap maximum velocity
			velocity.x = MAX_VELOCITY;
			getAcceleration().x = 0;
		} else if (velocity.x < 0) {
			// Can't go backwards
			velocity.x = 0;
			getAcceleration().x = 0;
		}
	}
	
	public void applyVelocity()
	{
		getPosition().add(new PVector(0, getVelocity().y));
		checkPosition();
	}
	
	public void checkPosition()
	{
		PVector position = getPosition();
		if (position.y > GROUND_LEVEL) {
			// Can't fall below ground level
			position.y = GROUND_LEVEL;
			getVelocity().y = 0;
			getAcceleration().y = 0;
		}
	}
	
	public void advanceAnimationFrame()
	{
		frameSpeed += getVelocity().x / (MAX_VELOCITY / 2);
		while (frameSpeed > 1) {
			running.update();
			setImage(running.getCurrentFrame());
			frameSpeed--;
		}
	}
	
	public void applyNegativeAcceleration()
	{
		getAcceleration().x += GROUND_FRICTION * getVelocity().x;
		checkAcceleration();
	}
	
	public void checkAcceleration()
	{
		PVector acceleration = getAcceleration();
		if (acceleration.x > MAX_ACCELERATION) {
			acceleration.x = MAX_ACCELERATION;
		}
	}
	
	public void updateDead()
	{
		getVelocity().add(getAcceleration());
		getPosition().add(getVelocity());
	}
	
	public void die() {
		if (isAlive()) {
			PVector velocity = getVelocity();
			PVector acceleration = getAcceleration();
			
			velocity.y = -12;
			velocity.x = 0;
			
			acceleration.y = (float) 0.5;
			acceleration.x = 0;
			
			getIsAliveDataSource().set(false);
		}
	}
	
	public void draw(PGraphics g) {
		PVector initialPosition = getInitialPosition();
		PVector position = getPosition();
		g.image(getImage(), initialPosition.x, position.y);
		
		if (!isOnScreen(g)) {
			getIsOnScreenDataSource().set(false);
		}
	}
	
	public Rectangle getBounds() {
		Rectangle bounds = super.getBounds(); 
		bounds.setLocation((int) getInitialPosition().x, (int) getPosition().y);
		return bounds;
	}
}
